﻿using FFmpeg.AutoGen;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace VideoPlayer.FFmpegHandle
{
    public unsafe class StreamDecoder : IDisposable
    {
        /// <summary>
        /// 帧上下文
        /// </summary>
        private readonly AVFormatContext* _pFormatContext;

        /// <summary>
        /// 视频解码上下文
        /// </summary>
        private readonly AVCodecContext* _videoCodecContext;

        /// <summary>
        /// 音频解码上下文
        /// </summary>
        private readonly AVCodecContext* _audioCodecContext;

        /// <summary>
        /// //流队列中，视频流所在的位置
        /// </summary>
        private readonly int video_index = -1;

        /// <summary>
        /// //流队列中，音频流所在的位置
        /// </summary>
        private readonly int audio_index = -1;

        /// <summary>
        /// 转码后输出的视频帧
        /// </summary>
        private readonly AVFrame* _pFrame;

        /// <summary>
        /// 解码前数据包
        /// </summary>
        private readonly AVPacket* _pPacket;

        /// <summary>
        /// 音频声道数量
        /// </summary>
        private readonly int nb_channels;

        /// <summary>
        /// 格式转换上下文
        /// </summary>
        private readonly SwrContext* audio_convert_ctx;

        /// <summary>
        /// 输出缓存大小
        /// </summary>
        private int audio_out_buffer_size;

        /// <summary>
        /// 输出缓存
        /// </summary>
        private byte* audio_out_buffer;

        /// <summary>
        /// 输出的采样格式 16bit PCM
        /// </summary>
        private AVSampleFormat out_sample_fmt = AVSampleFormat.AV_SAMPLE_FMT_S16;

        /// <summary>
        /// 输出的声道布局：立体声
        /// </summary>
        private ulong out_ch_layout = ffmpeg.AV_CH_LAYOUT_STEREO;

        /// <summary>
        /// 视频基时间
        /// </summary>
        public double Video_TimeBase = 0;

        /// <summary>
        /// 音频基时间
        /// </summary>
        public double Audio_TimeBase = 0;



        #region 可以输出的参数

        /// <summary>
        /// 当前帧时间
        /// </summary>
        public long indexTime { get; private set; } = 0;

        /// <summary>
        /// 跳转间隔时间 秒
        /// </summary>
        public long JumpTime = 0;

        /// <summary>
        /// 跳转到时间 秒
        /// </summary>
        public long JumpDate = 0;

        /// <summary>
        /// 视频帧率
        /// </summary>
        public float frame_rate { get; }

        /// <summary>
        /// 文件播放时长，单位：毫秒
        /// </summary>
        public long m_Duration { get; }

        /// <summary>
        /// 采样率
        /// </summary>
        public int sample_rate { get; }

        /// <summary>
        /// 画面大小
        /// </summary>
        public Size FrameSize { get; }

        /// <summary>
        /// 像素格式
        /// </summary>
        public AVPixelFormat PixelFormat { get; }

        #endregion

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="url"></param>
        public StreamDecoder(string url)
        {
            ffmpeg.av_register_all();//注册组件
            ffmpeg.avformat_network_init();//支持网络流
            _pFormatContext = ffmpeg.avformat_alloc_context();

            var pFormatContext = _pFormatContext;
            //打开文件
            ffmpeg.avformat_open_input(&pFormatContext, url, null, null).ThrowExceptionIfError();
            //查找流信息
            ffmpeg.avformat_find_stream_info(_pFormatContext, null).ThrowExceptionIfError();


            for (var i = 0; i < _pFormatContext->nb_streams; i++)
            {
                if (_pFormatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
                {
                    video_index = i;
                }
                if (_pFormatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
                {
                    if (audio_index == -1)
                        audio_index = i;
                }
            }
            //音视频流都没有找到
            if (video_index == -1 && audio_index == -1)
            {
                throw new InvalidOperationException("没有音频视频流");
            }
            //设置上下文
            _videoCodecContext = _pFormatContext->streams[video_index]->codec;
            _audioCodecContext = _pFormatContext->streams[audio_index]->codec;
            //找解码器
            AVCodec* video_codec = ffmpeg.avcodec_find_decoder(_videoCodecContext->codec_id);
            AVCodec* audio_codec = ffmpeg.avcodec_find_decoder(_audioCodecContext->codec_id);

            if (video_codec == null && audio_codec == null) throw new InvalidOperationException("没有找到解码器");


            ffmpeg.avcodec_open2(_audioCodecContext, audio_codec, null).ThrowExceptionIfError();
            ffmpeg.avcodec_open2(_videoCodecContext, video_codec, null).ThrowExceptionIfError();
            //CodecName = ffmpeg.avcodec_get_name(codecId);
            FrameSize = new Size(_videoCodecContext->width, _videoCodecContext->height);
            PixelFormat = _videoCodecContext->pix_fmt;

            frame_rate = (float)_pFormatContext->streams[video_index]->avg_frame_rate.num / (float)_pFormatContext->streams[video_index]->avg_frame_rate.den;//每秒多少帧
            Video_TimeBase = ffmpeg.av_q2d(_pFormatContext->streams[video_index]->time_base);
            Audio_TimeBase = ffmpeg.av_q2d(_pFormatContext->streams[audio_index]->time_base);

            m_Duration = (long)_pFormatContext->duration / 1000;//(_pFormatContext->streams[video_index]->duration * Video_TimeBase); //_pFormatContext->duration / 1000;
            if (m_Duration <= 0)
                m_Duration = _pFormatContext->duration / 1000;

            //======音频转码准备======start======
            AVSampleFormat in_sample_fmt = _audioCodecContext->sample_fmt;//输入的采样格式
            sample_rate = _audioCodecContext->sample_rate;//输入的采样率
            ulong in_ch_layout = _audioCodecContext->channel_layout;//输入的声道布局

            audio_convert_ctx = ffmpeg.swr_alloc();
            ffmpeg.swr_alloc_set_opts(audio_convert_ctx, (long)out_ch_layout, out_sample_fmt, sample_rate, (long)in_ch_layout, in_sample_fmt, sample_rate, 0, null);
            ffmpeg.swr_init(audio_convert_ctx);

            nb_channels = ffmpeg.av_get_channel_layout_nb_channels(out_ch_layout);//获取声道个数
            audio_out_buffer = (byte*)ffmpeg.av_malloc((ulong)sample_rate * 2);//存储pcm数据
            //======音频转码准备======end======

            _pPacket = ffmpeg.av_packet_alloc();
            _pFrame = ffmpeg.av_frame_alloc();
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            ffmpeg.av_frame_unref(_pFrame);
            ffmpeg.av_free(_pFrame);

            ffmpeg.av_packet_unref(_pPacket);
            ffmpeg.av_free(_pPacket);

            ffmpeg.avcodec_close(_audioCodecContext);
            ffmpeg.avcodec_close(_videoCodecContext);
            var pFormatContext = _pFormatContext;
            ffmpeg.avformat_close_input(&pFormatContext);
        }

        /// <summary>
        /// 读取一帧 -1：未取到 1：音频 2：视频  4:结束
        /// </summary>
        /// <returns></returns>
        public int read_frame()
        {
            ffmpeg.av_frame_unref(_pFrame);
            AVFrame* original_audio_frame = ffmpeg.av_frame_alloc();
            try
            {
                int ret = -1;
                //是否从packet中解出一帧，0为未解出
                int got_frame;
                if (JumpTime > 0 || JumpDate > 0)//
                    Jump();
                //从packet中解出来的原始音视频帧
                if (ffmpeg.av_read_frame(_pFormatContext, _pPacket) == 0)
                {
                    if (_pPacket->stream_index == video_index)
                    {
                        ffmpeg.avcodec_send_packet(_videoCodecContext, _pPacket).ThrowExceptionIfError();
                        int error = ffmpeg.avcodec_receive_frame(_videoCodecContext, _pFrame).ThrowExceptionIfError();
                        indexTime = (long)(_pFrame->pts * Video_TimeBase * 1000);//(int)(_pFrame->pts * TimeBase);//ffmpeg.av_q2d(_pFormatContext->streams[video_index]->time_base)
                        //if ((int)(indexTime / 1000) == (int)(m_Duration / 1000))
                        //    ret = 4;
                        //else
                            ret = 2;
                    }
                    else if (_pPacket->stream_index == audio_index)
                    {
                        if (ffmpeg.avcodec_decode_audio4(_audioCodecContext, original_audio_frame, &got_frame, _pPacket) >= 0)
                        {
                            if (got_frame > 0)
                            {
                                fixed (byte** audio_out_buffer_ = &audio_out_buffer)
                                {
                                    //音频格式转换
                                    ffmpeg.swr_convert(audio_convert_ctx,//音频转换上下文
                                    audio_out_buffer_,//输出缓存
                                    sample_rate * 2,//每次输出大小
                                    original_audio_frame->extended_data,//输入数据
                        original_audio_frame->nb_samples);//输入

                                }
                                audio_out_buffer_size = ffmpeg.av_samples_get_buffer_size(null, nb_channels, original_audio_frame->nb_samples, out_sample_fmt, 1);
                                indexTime = (long)(original_audio_frame->pts * Audio_TimeBase * 1000);
                                ret = 1;
                            }
                        }
                    }
                }
                return ret;
            }
            catch (Exception e)
            {
                return -1;
            }
            finally
            {
                ffmpeg.av_packet_unref(_pPacket);
                ffmpeg.av_free(original_audio_frame);
            }
        }


        /// <summary>
        /// 获取视频帧
        /// </summary>
        /// <returns></returns>
        public AVFrame get_video_frame()
        {
            return *_pFrame;
        }

        /// <summary>
        /// 获取音频帧
        /// </summary>
        /// <returns></returns>
        public char* get_audio_frame()
        {
            return (char*)audio_out_buffer;//Marshal.AllocHGlobal(audio_out_buffer_size);
        }

        //获取音频缓存大小
        public int get_audio_buffer_size()
        {
            return audio_out_buffer_size;
        }

        /// <summary>
        /// 快进
        /// </summary>
        private void Jump()
        {
            try
            {
                if (JumpTime > 0)
                {
                    ffmpeg.av_seek_frame(_pFormatContext, -1, (long)JumpTime, ffmpeg.AVSEEK_FLAG_FRAME).ThrowExceptionIfError();
                    JumpTime = 0;
                }
                else
                if (JumpDate > 0)
                {
                    //
                    ffmpeg.av_seek_frame(_pFormatContext, -1, JumpDate * ffmpeg.AV_TIME_BASE, ffmpeg.AVSEEK_FLAG_FRAME).ThrowExceptionIfError();
                    JumpDate = 0;
                }
            }
            catch
            {
                //JumpTime = 0;
                JumpDate = 0;
            }
        }

        /// <summary>
        /// 获取当前视频上下文信息
        /// </summary>
        /// <returns></returns>
        public IReadOnlyDictionary<string, string> GetContextInfo()
        {
            AVDictionaryEntry* tag = null;
            var result = new Dictionary<string, string>();
            while ((tag = ffmpeg.av_dict_get(_pFormatContext->metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
            {
                var key = Marshal.PtrToStringAnsi((IntPtr)tag->key);
                var value = Marshal.PtrToStringAnsi((IntPtr)tag->value);
                result.Add(key, value);
            }
            return result;
        }
    }
}
